﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace ItemsViewer
{
    /// <summary>
    /// This class represent a Virtualizing WrapPanel Animated.
    /// </summary>
    public class AnimatedVirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
    {
        /// <summary>
        /// The infinite Size.
        /// </summary>
        private Size infiniteSize;

        /// <summary>
        /// The IItemContainerGenerator.
        /// </summary>
        private IItemContainerGenerator generator;

        /// <summary>
        /// The current items control.
        /// </summary>
        private ItemsControl itemsControl;

        /// <summary>
        /// The extend size.
        /// </summary>
        private Size extent;

        /// <summary>
        /// The viewPort Size.
        /// </summary>
        private Size viewport;

        /// <summary>
        /// The current item width.
        /// </summary>
        private double itemWidth;

        /// <summary>
        /// The current item height.
        /// </summary>
        private double itemHeight;

        /// <summary>
        /// The scroll owner.
        /// </summary>
        private ScrollViewer scrollOwner;

        /// <summary>
        /// The current offset.
        /// </summary>
        private Vector offset;

        /// <summary>
        /// The animation lenght.
        /// </summary>
        private TimeSpan animationLength;

        /// <summary>
        /// Can Horizontally Scroll.
        /// </summary>
        private bool canHorizontallyScroll;

        /// <summary>
        /// Can Vertically Scroll.
        /// </summary>
        private bool canVerticallyScroll;

        /// <summary>
        /// The render tranform.
        /// </summary>
        private TranslateTransform renderTransform;

        /// <summary>
        /// The scale Trasnform
        /// </summary>
        private ScaleTransform scaleTransform;

        /// <summary>
        /// This value indicates whether the scroll view.
        /// </summary>
        private bool isScrollEvent;

        /// <summary>
        /// This value indicates whether the size change.
        /// </summary>
        private bool isSizeChanged;

        /// <summary>
        /// The previous Start Position.
        /// </summary>
        private int previousStartPosition;

        /// <summary>
        /// The current Positions of UIElement.
        /// </summary>
        private Dictionary<UIElement, Rect> currentPositions;

        /// <summary>
        /// Initializes a new instance of the AnimatedVirtualizingWrapPanel class.
        /// </summary>
        public AnimatedVirtualizingWrapPanel()
        {
            // Set animation Length.
            this.animationLength = TimeSpan.FromMilliseconds(800);

            // Set default size.
            this.infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
            this.extent = new Size(0, 0);

            this.currentPositions = new Dictionary<UIElement, Rect>();

            // Init Transform
            this.renderTransform = new TranslateTransform();
            this.scaleTransform = new ScaleTransform(1, 1);
            var group = new TransformGroup();
            group.Children.Add(this.renderTransform);
            group.Children.Add(this.scaleTransform);
            this.RenderTransform = group;
            this.CanAnimate = true;
        }

        /// <summary>
        /// Gets or sets Animation Length.
        /// </summary>
        public TimeSpan AnimationLength
        {
            get { return this.animationLength; }
            set { this.animationLength = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the view can scroll Horizontally.
        /// </summary>
        public bool CanHorizontallyScroll
        {
            get
            {
                return this.canHorizontallyScroll;
            }

            set
            {
                this.canHorizontallyScroll = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the view can scroll vertically.
        /// </summary>
        public bool CanVerticallyScroll
        {
            get
            {
                return this.canVerticallyScroll;
            }

            set
            {
                this.canVerticallyScroll = value;
            }
        }

        /// <summary>
        /// Gets Extent Height.
        /// </summary>
        public double ExtentHeight
        {
            get
            {
                return this.extent.Height;
            }
        }

        /// <summary>
        /// Gets Extent Width.
        /// </summary>
        public double ExtentWidth
        {
            get
            {
                return this.extent.Width;
            }
        }

        /// <summary>
        /// Gets or sets the scrollOwner.
        /// </summary>
        public ScrollViewer ScrollOwner
        {
            get
            {
                return this.scrollOwner;
            }

            set
            {
                this.scrollOwner = value;
            }
        }

        /// <summary>
        /// Gets Horizontal Offset.
        /// </summary>
        public double HorizontalOffset
        {
            get
            {
                return this.offset.X;
            }
        }

        /// <summary>
        /// Gets Vertical Offset.
        /// </summary>
        public double VerticalOffset
        {
            get
            {
                return this.offset.Y;
            }
        }

        /// <summary>
        /// Gets Viewport Height.
        /// </summary>
        public double ViewportHeight
        {
            get
            {
                return this.viewport.Height;
            }
        }

        /// <summary>
        /// Gets Viewport Width.
        /// </summary>
        public double ViewportWidth
        {
            get
            {
                return this.viewport.Width;
            }
        }

        /// <summary>
        ///  Gets or sets a value indicating whether the view can animate.
        /// </summary>
        public bool CanAnimate { get; set; }

        /// <summary>
        /// Gets Start Position.
        /// </summary>
        private int StartPosition
        {
            get
            {
                int firstItem = 0;
                if (this.itemHeight != 0)
                {
                    firstItem = (int)((this.VerticalOffset / this.itemHeight) * this.ItemsPerLine);
                }

                return firstItem;
            }
        }

        /// <summary>
        /// Gets Item per line.
        /// </summary>
        private int ItemsPerLine
        {
            get
            {
                return (int)(this.ViewportWidth / this.itemWidth);
            }
        }

        /// <summary>
        /// Line Down.
        /// </summary>
        public void LineDown()
        {
            this.SetVerticalOffset(this.VerticalOffset + this.itemWidth);
        }

        /// <summary>
        /// Line Up for scroll.
        /// </summary>
        public void LineUp()
        {
            this.SetVerticalOffset(this.VerticalOffset - this.itemWidth);
        }

        /// <summary>
        /// Line Left for scroll.
        /// </summary>
        public void LineLeft()
        {
            this.SetHorizontalOffset(this.HorizontalOffset - this.itemWidth);
        }

        /// <summary>
        /// Line right for scroll.
        /// </summary>
        public void LineRight()
        {
            this.SetHorizontalOffset(this.HorizontalOffset + this.itemWidth);
        }

        /// <summary>
        /// Mouse Wheel Down for scroll.
        /// </summary>
        public void MouseWheelDown()
        {
        }

        /// <summary>
        /// Mouse Wheel Up for scroll.
        /// </summary>
        public void MouseWheelUp()
        {
        }

        /// <summary>
        /// Mouse Wheel Left for scroll.
        /// </summary>
        public void MouseWheelLeft()
        {
            this.SetHorizontalOffset(this.HorizontalOffset - (3 * this.itemWidth));
        }

        /// <summary>
        /// Mouse Wheel Rightf for scroll.
        /// </summary>
        public void MouseWheelRight()
        {
            this.SetHorizontalOffset(this.HorizontalOffset + (3 * this.itemWidth));
        }

        /// <summary>
        /// Page Down for scroll.
        /// </summary>
        public void PageDown()
        {
            this.SetVerticalOffset(this.VerticalOffset + this.ViewportHeight);
        }

        /// <summary>
        /// Page Up for scroll.
        /// </summary>
        public void PageUp()
        {
            this.SetVerticalOffset(this.VerticalOffset - this.ViewportHeight);
        }

        /// <summary>
        /// Page Left for scroll.
        /// </summary>
        public void PageLeft()
        {
            this.SetHorizontalOffset(this.HorizontalOffset - this.ViewportWidth);
        }

        /// <summary>
        /// Page Right for scroll.
        /// </summary>
        public void PageRight()
        {
            this.SetHorizontalOffset(this.HorizontalOffset + this.ViewportWidth);
        }

        /// <summary>
        /// Set Horizontal Offset. 
        /// </summary>
        /// <param name="offset">The new value.</param>
        public void SetHorizontalOffset(double offset)
        {
            offset = Math.Max(0, Math.Min(offset, this.ExtentWidth - this.ViewportWidth));
            if (offset != this.offset.X)
            {
                this.offset.X = offset;
                this.renderTransform.X = 0 - offset;
                if (!this.isSizeChanged)
                {
                    this.isScrollEvent = true;
                    this.previousStartPosition = this.StartPosition;
                }
            }

            InvalidateMeasure();
            InvalidateArrange();
            this.isSizeChanged = false;
        }

        /// <summary>
        /// Set Vertical Offset. 
        /// </summary>
        /// <param name="offset">The new value.</param>
        public void SetVerticalOffset(double offset)
        {
            offset = Math.Max(0, Math.Min(offset, this.ExtentHeight - this.ViewportHeight));
            if (offset != this.offset.Y)
            {
                this.offset.Y = offset;
                this.renderTransform.Y = 0 - offset;
                if (!this.isSizeChanged)
                {
                    this.isScrollEvent = true;
                    this.previousStartPosition = this.StartPosition;
                }
            }

            InvalidateMeasure();
            InvalidateArrange();
            this.isSizeChanged = false;
        }

        /// <summary>
        /// No implemented.
        /// </summary>
        /// <param name="visual">The visual.</param>
        /// <param name="rectangle">The rectangle.</param>
        /// <returns>Return new Rect.</returns>
        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            return new Rect();
        }

        /// <summary>
        /// Scroll view to item target.
        /// </summary>
        /// <param name="item">The item target.</param>
        public void ScrollToItem(object item)
        {
            int pos = 0;
            foreach (var o in this.itemsControl.Items)
            {
                if (o.Equals(item))
                {
                    break;
                }

                pos++;
            }

            bool continu = true;
            foreach (var child in this.InternalChildren)
            {
                if ((child as FrameworkElement).DataContext == item)
                {
                    continu = false;
                    break;
                }
            }

            if (continu)
            {
                var v = (pos * this.itemHeight) / this.ItemsPerLine;

                if (v != 0)
                {
                    DoubleAnimation vertScrollAnim = new DoubleAnimation();
                    vertScrollAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
                    var lenghtAnimation = Math.Abs(v - this.VerticalOffset) * 1000 / this.ExtentHeight;
                    vertScrollAnim.Duration = new Duration(TimeSpan.FromMilliseconds(lenghtAnimation));
                    Storyboard storyboardScroll = new Storyboard();
                    storyboardScroll.Children.Add(vertScrollAnim);
                    Storyboard.SetTarget(vertScrollAnim, this.ScrollOwner);
                    Storyboard.SetTargetProperty(vertScrollAnim, new PropertyPath(AnimatedScrollViewer.CurrentVerticalOffsetProperty));
                    vertScrollAnim.From = this.VerticalOffset;
                    vertScrollAnim.To = v;
                    storyboardScroll.Begin();
                }
                else
                {
                    this.SetVerticalOffset(0);
                }
            }
        }

        /// <summary>
        /// Remove Disconnected Item.
        /// </summary>
        public void RemoveDisconnectedItem()
        {
            var disconnectedItem = typeof(System.Windows.Data.BindingExpressionBase).GetField("DisconnectedItem", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
            for (int i = InternalChildren.Count - 1; i >= 0; i--)
            {
                var child = this.InternalChildren[i];
                if ((child as FrameworkElement).DataContext == disconnectedItem)
                {
                    RemoveInternalChildRange(i, 1);
                    break;
                }
            }
        }

        /// <summary>
        /// Override measure.
        /// </summary>
        /// <param name="availableSize">The availableSize.</param>
        /// <returns>Return the new viewport size.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            double curX = 0, curY = 0, curLineHeight = 0;
            int currentStartPosition = this.StartPosition;
            int currentEndPosition = this.EndPosition(null);

            if (this.VerticalOffset > 0)
            {
                if ((this.RenderSize.Height != availableSize.Height) || (this.RenderSize.Width != availableSize.Width))
                {
                    currentStartPosition = this.previousStartPosition;
                    currentEndPosition = this.EndPosition(currentStartPosition);
                }
            }

            GeneratorPosition start = this.generator.GeneratorPositionFromIndex(currentStartPosition);
            int childIndex = (start.Offset == 0) ? start.Index : start.Index + 1;

            using (this.generator.StartAt(start, GeneratorDirection.Forward, true))
            {
                for (int itemIndex = currentStartPosition; itemIndex <= currentEndPosition; ++itemIndex, ++childIndex)
                {
                    bool newlyRealized;

                    // Get or create the child
                    UIElement child = this.generator.GenerateNext(out newlyRealized) as UIElement;
                    if (child == null)
                    {
                        continue;
                    }

                    if (newlyRealized)
                    {
                        // Figure out if we need to insert the child at the end or somewhere in the middle
                        if (childIndex >= this.InternalChildren.Count)
                        {
                            this.AddInternalChild(child);
                        }
                        else
                        {
                            this.InsertInternalChild(childIndex, child);
                        }

                        this.generator.PrepareItemContainer(child);
                    }

                    child.Measure(this.infiniteSize);

                    if ((this.itemHeight == 0) || (this.itemWidth == 0))
                    {
                        this.itemWidth = child.DesiredSize.Width;
                        this.itemHeight = child.DesiredSize.Height;
                    }

                    if (curX + child.DesiredSize.Width > availableSize.Width)
                    {
                        // Wrap to next line
                        curY += curLineHeight;
                        curX = 0;
                        curLineHeight = 0;
                    }

                    curX += child.DesiredSize.Width;
                    if (child.DesiredSize.Height > curLineHeight)
                    {
                        curLineHeight = child.DesiredSize.Height;
                    }
                }
            }

            curY += curLineHeight;

            this.viewport = availableSize;
            this.viewport.Width = double.IsPositiveInfinity(availableSize.Width) ?
                curX : availableSize.Width;
            this.viewport.Height = double.IsPositiveInfinity(availableSize.Height) ?
                curY : availableSize.Height;

            this.extent = new Size(availableSize.Width, (((double)this.itemsControl.Items.Count / (double)this.ItemsPerLine) * this.itemHeight) + this.itemHeight);

            this.CleanUpItems(currentStartPosition, currentEndPosition);

            if (this.ScrollOwner != null)
            {
                this.ScrollOwner.InvalidateScrollInfo();
            }

            return this.viewport;
        }

        /// <summary>
        /// Override Arrange 
        /// </summary>
        /// <param name="finalSize">The finalSize.</param>
        /// <returns>The new finalSize.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (this.InternalChildren == null || this.InternalChildren.Count == 0)
            {
                return finalSize;
            }

            if ((this.RenderSize.Height != finalSize.Height) || (this.RenderSize.Width != finalSize.Width))
            {
                return finalSize;
            }

            // Init local variable.
            double curX = 0, curY = 0, curLineHeight = 0;
            Dictionary<UIElement, Rect> tmpPositions = new Dictionary<UIElement, Rect>();

            TranslateTransform trans = null;
            foreach (UIElement child in this.InternalChildren)
            {
                trans = child.RenderTransform as TranslateTransform;
                if (trans == null)
                {
                    child.RenderTransformOrigin = new Point(0, 0);
                    trans = new TranslateTransform();
                    child.RenderTransform = trans;
                }

                if (curX + child.DesiredSize.Width > finalSize.Width)
                {
                    // Wrap to next line
                    curY += curLineHeight;
                    curX = 0;
                    curLineHeight = 0;
                }

                bool continu = true;
                if (this.currentPositions.ContainsKey(child))
                {
                    if ((this.currentPositions[child].Left == curX + this.HorizontalOffset) && (this.currentPositions[child].Top == curY + this.VerticalOffset))
                    {
                        continu = false;
                    }
                }

                // If the current position of child changed.
                if (continu)
                {
                    child.Arrange(new Rect(this.HorizontalOffset, this.VerticalOffset, child.DesiredSize.Width, child.DesiredSize.Height));

                    if (this.isScrollEvent || !this.CanAnimate)
                    {
                        trans.X = curX;
                        trans.Y = curY;
                    }
                    else
                    {
                        // Animate translation.
                        DoubleAnimation xAnim = new DoubleAnimation(curX, this.animationLength);
                        xAnim.FillBehavior = FillBehavior.Stop;
                        xAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
                        Storyboard.SetTargetProperty(xAnim, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
                        Storyboard.SetTarget(xAnim, child);

                        DoubleAnimation yAnim = new DoubleAnimation(curY, this.animationLength);
                        yAnim.FillBehavior = FillBehavior.Stop;
                        yAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
                        Storyboard.SetTargetProperty(yAnim, new PropertyPath("RenderTransform.(TranslateTransform.Y)"));
                        Storyboard.SetTarget(yAnim, child);

                        Storyboard st = new Storyboard();
                        st.Children.Add(xAnim);
                        st.Children.Add(yAnim);
                        st.Completed += (s, e) =>
                        {
                            var stBoard = ((ClockGroup)s).Timeline as Storyboard;
                            var doubleAnimationX = stBoard.Children[0] as DoubleAnimation;
                            var doubleAnimationY = stBoard.Children[1] as DoubleAnimation;
                            var l = Storyboard.GetTarget(doubleAnimationX) as FrameworkElement;
                            var translate = l.RenderTransform as TranslateTransform;
                            translate.X = (double)doubleAnimationX.To;
                            translate.Y = (double)doubleAnimationY.To;
                        };
                        st.Begin();
                    }
                }

                tmpPositions.Add(child, new Rect(curX + this.HorizontalOffset, curY + this.VerticalOffset, child.DesiredSize.Width, child.DesiredSize.Height));

                curX += child.DesiredSize.Width;
                if (child.DesiredSize.Height > curLineHeight)
                {
                    curLineHeight = child.DesiredSize.Height;
                }
            }

            this.isScrollEvent = false;
            this.currentPositions.Clear();
            foreach (var pos in tmpPositions)
            {
                this.currentPositions.Add(pos.Key, pos.Value);
            }

            this.CanAnimate = true;

            return finalSize;
        }

        /// <summary>
        /// Initialize the local data.
        /// </summary>
        /// <param name="e">The EventArgs parameter.</param>
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            var children = this.InternalChildren;
            this.generator = this.ItemContainerGenerator;
            this.itemsControl = ItemsControl.GetItemsOwner(this);
        }

        /// <summary>
        /// On render Size changed set Vertical offset.
        /// </summary>
        /// <param name="sizeInfo">The size upadted.</param>
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            if (this.ViewportWidth != 0)
            {
                if ((this.VerticalOffset != 0) && (this.EndPosition(this.previousStartPosition) != this.itemsControl.Items.Count - 1))
                {
                    if (this.ItemsPerLine != 0)
                    {
                        var v = (this.previousStartPosition * this.itemHeight) / this.ItemsPerLine;
                        this.isSizeChanged = true;
                        this.SetVerticalOffset(v);
                    }
                }
                else
                {
                    this.SetVerticalOffset(0);
                }
            }

            base.OnRenderSizeChanged(sizeInfo);
        }

        /// <summary>
        /// On Mouse Wheel scroll or zoom the content.
        /// </summary>
        /// <param name="e">The MouseWheelEventArgs parameter.</param>
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            // If Keyboard IsKeyDown Zoom.
            if (Keyboard.IsKeyDown(Key.LeftCtrl))
            {
                Point p = e.GetPosition(this.ScrollOwner);
                this.ZoomContent(p, e.Delta);
            }
            else
            {
                // Scroll
                if (this.ScrollOwner is AnimatedScrollViewer)
                {
                    DoubleAnimation vertScrollAnim = new DoubleAnimation();
                    vertScrollAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
                    Storyboard storyboardScroll = new Storyboard();
                    storyboardScroll.Children.Add(vertScrollAnim);
                    Storyboard.SetTarget(vertScrollAnim, this.ScrollOwner);
                    Storyboard.SetTargetProperty(vertScrollAnim, new PropertyPath(AnimatedScrollViewer.CurrentVerticalOffsetProperty));
                    vertScrollAnim.From = this.VerticalOffset;
                    vertScrollAnim.Duration = new Duration(TimeSpan.FromMilliseconds(800));

                    if (e.MiddleButton == MouseButtonState.Pressed)
                    {
                        if (e.Delta < 0)
                        {
                            vertScrollAnim.To = this.VerticalOffset + this.itemHeight;
                        }
                        else
                        {
                            vertScrollAnim.To = this.VerticalOffset - this.itemHeight;
                        }
                    }
                    else
                    {
                        if (e.Delta < 0)
                        {
                            vertScrollAnim.To = this.VerticalOffset + (this.ExtentHeight / 3);
                        }
                        else
                        {
                            vertScrollAnim.To = this.VerticalOffset - (this.ExtentHeight / 3);
                        }
                    }

                    storyboardScroll.Begin();
                }
            }

            base.OnMouseWheel(e);
        }

        /// <summary>
        /// On Key Down move selected item.
        /// </summary>
        /// <param name="e">The KeyEventArgs parameter.</param>
        protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
        {
            UIElement selected = (UIElement)Keyboard.FocusedElement;

            if (this.currentPositions.ContainsKey(selected))
            {
                Rect rect = this.currentPositions[selected];
                switch (e.Key)
                {
                    case Key.Down:

                        if (rect.BottomLeft.Y - this.VerticalOffset > this.ViewportHeight)
                        {
                            this.LineDown();
                        }

                        break;
                    case Key.Up:

                        if (rect.TopLeft.Y - this.VerticalOffset == 0)
                        {
                            this.LineUp();
                        }

                        break;
                }
            }

            selected.Focus();
            base.OnKeyDown(e);
        }

        /// <summary>
        /// Return end position.
        /// </summary>
        /// <param name="customStartPosition">The custom start position.</param>
        /// <returns>The end position.</returns>
        private int EndPosition(int? customStartPosition)
        {
            int lastItem = 0;
            if (this.itemHeight != 0)
            {
                double lines = this.ViewportHeight / this.itemHeight;
                double items = lines * this.ItemsPerLine;

                if (customStartPosition == null)
                {
                    lastItem = this.StartPosition +
                      (int)Math.Ceiling(items);
                }
                else
                {
                    lastItem = (int)customStartPosition +
                     (int)Math.Ceiling(items);
                }

                lastItem--;

                lastItem = Math.Min(lastItem, this.itemsControl.Items.Count - 1);
            }

            return lastItem;
        }

        /// <summary>
        /// Clean up items.
        /// </summary>
        /// <param name="firstVisibleItemIndex">The firstVisible Item Index.</param>
        /// <param name="lastVisibleItemIndex">The lastVisible Item Index.</param>
        private void CleanUpItems(int firstVisibleItemIndex, int lastVisibleItemIndex)
        {
            UIElementCollection children = this.InternalChildren;

            for (int i = children.Count - 1; i >= 0; i--)
            {
                // Map a child index to an item index by going through a generator position
                GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
                int itemIndex = this.generator.IndexFromGeneratorPosition(childGeneratorPos);
                if (itemIndex < firstVisibleItemIndex || itemIndex > lastVisibleItemIndex)
                {
                    RemoveInternalChildRange(i, 1);
                    this.generator.Remove(childGeneratorPos, 1);
                }
            }
        }

        /// <summary>
        /// Zoom Content.
        /// </summary>
        /// <param name="center">The center of transformation.</param>
        /// <param name="delta">The delta.</param>
        private void ZoomContent(Point center, int delta)
        {
            double animScaleLength = 800;
            double animCenterLength = 500;
            if (delta < 0)
            {
                animScaleLength = 400;
                animCenterLength = 250;
            }

            DoubleAnimation scaleXAnim = new DoubleAnimation();
            scaleXAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
            scaleXAnim.Duration = new Duration(TimeSpan.FromMilliseconds(animScaleLength));
            scaleXAnim.From = this.scaleTransform.ScaleX;

            DoubleAnimation scaleYAnim = new DoubleAnimation();
            scaleYAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
            scaleYAnim.Duration = new Duration(TimeSpan.FromMilliseconds(animScaleLength));
            scaleYAnim.From = this.scaleTransform.ScaleY;

            DoubleAnimation centerXAnim = new DoubleAnimation();
            centerXAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
            centerXAnim.Duration = new Duration(TimeSpan.FromMilliseconds(animCenterLength));
            centerXAnim.From = this.scaleTransform.CenterX;
            centerXAnim.To = center.X;

            DoubleAnimation centerYAnim = new DoubleAnimation();
            centerYAnim.EasingFunction = new ExponentialEase() { EasingMode = System.Windows.Media.Animation.EasingMode.EaseInOut };
            centerYAnim.Duration = new Duration(TimeSpan.FromMilliseconds(animCenterLength));
            centerYAnim.From = this.scaleTransform.CenterY;
            centerYAnim.To = center.Y;

            Storyboard storyboardScale = new Storyboard();
            storyboardScale.Children.Add(scaleXAnim);
            storyboardScale.Children.Add(scaleYAnim);
            storyboardScale.Children.Add(centerXAnim);
            storyboardScale.Children.Add(centerYAnim);

            Storyboard.SetTarget(scaleXAnim, this);
            Storyboard.SetTargetProperty(scaleXAnim, new PropertyPath("RenderTransform.Children[1].ScaleX"));

            Storyboard.SetTarget(scaleYAnim, this);
            Storyboard.SetTargetProperty(scaleYAnim, new PropertyPath("RenderTransform.Children[1].ScaleY"));

            Storyboard.SetTarget(centerXAnim, this);
            Storyboard.SetTargetProperty(centerXAnim, new PropertyPath("RenderTransform.Children[1].CenterX"));

            Storyboard.SetTarget(centerYAnim, this);
            Storyboard.SetTargetProperty(centerYAnim, new PropertyPath("RenderTransform.Children[1].CenterY"));

            if (delta > 0)
            {
                scaleXAnim.To = this.scaleTransform.ScaleX + (0.8 * Math.Floor(this.scaleTransform.ScaleX));
                scaleYAnim.To = this.scaleTransform.ScaleY + (0.8 * Math.Floor(this.scaleTransform.ScaleY));
            }
            else
            {
                if (this.scaleTransform.ScaleX - (0.8 * Math.Floor(this.scaleTransform.ScaleX)) >= 1)
                {
                    scaleXAnim.To = this.scaleTransform.ScaleX - (0.8 * Math.Floor(this.scaleTransform.ScaleX));
                    scaleYAnim.To = this.scaleTransform.ScaleY - (0.8 * Math.Floor(this.scaleTransform.ScaleY));

                    if (this.scaleTransform.ScaleX - (0.8 * Math.Floor(this.scaleTransform.ScaleX)) == 1)
                    {
                        centerXAnim.To = 0;
                        centerYAnim.To = 0;
                    }
                }
            }

            storyboardScale.Begin();
        }
    }
}
